package editorsvg.editor.documento;

import editorsvg.gui.generico.Actualizable;
import editorsvg.utilidades.Angulo;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 *
 * @author Jorge Berjano
 */
public class ListaElementos implements Actualizable, Cloneable {

    protected LinkedList<ElementoSVG> lista = new LinkedList<ElementoSVG>();
    //private Area contorno;
    private Path2D contorno;
    private ListIterator<ElementoSVG> iteradorAleatorio;

    public String toString() {
        return "Lista de elementos";//lista.toString();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        ListaElementos clon = (ListaElementos) super.clone();
        clon.lista = (LinkedList<ElementoSVG>) lista.clone();
        return clon;
    }

    @Override
    public void actualizar() {
        for (ElementoSVG elemento : lista) {
            elemento.actualizar();
        }
        actualizarContorno();
    }

    public List<ElementoSVG> getLista() {
        return lista;
    }

    public synchronized ElementoSVG get(int indice) {
        if (iteradorAleatorio != null) {
            if (iteradorAleatorio.nextIndex() == indice) {
                return iteradorAleatorio.next();
            } else if (iteradorAleatorio.previousIndex() == indice) {
                return iteradorAleatorio.previous();
            }
        }

        iteradorAleatorio = lista.listIterator(indice);
        return iteradorAleatorio.next();
    }

//    public void asignarLista(ListaElementos listaElementos) {
//        this.lista = listaElementos.lista;
//    }

    public synchronized void agregar(ElementoSVG elemento) {
        iteradorAleatorio = null;
        lista.addLast(elemento);
        actualizarContorno();
    }

    public synchronized void agregar(int indice, ElementoSVG elemento) {
        iteradorAleatorio = null;
        lista.add(indice, elemento);
        actualizarContorno();
    }

    public synchronized void eliminar(ElementoSVG elemento) {
        iteradorAleatorio = null;
        lista.remove(elemento);
        actualizarContorno();
    }

    public synchronized void limpiar() {
        iteradorAleatorio = null;
        lista.clear();
        actualizarContorno();
    }

    public boolean tieneElementos() {
        return lista.size() > 0;
    }

    public boolean contiene(ElementoSVG elemento) {
        return lista.contains(elemento);
    }

    public Rectangle2D getEncuadre() {
        if (lista.size() == 0) {
            return null;
        }

        Rectangle2D encuadre = null;
        for (ElementoSVG elemento : lista) {
            if (elemento.getEncuadre() == null) {
                continue;
            }
            if (encuadre == null) {
                encuadre = (Rectangle2D) elemento.getEncuadre().clone();
            } else {
                encuadre.add(elemento.getEncuadre());
            }
        }
        return encuadre;
    }

    public void actualizarContorno() {
        contorno = null;
    }
    
     public Shape getContorno() {
                
        if (!tieneElementos()) {
            contorno = null;
            return null;
        }
        
        
        if (contorno == null) {
            contorno = new Path2D.Double();
            for (ElementoSVG elemento : lista) {
                Shape contornoElemento = elemento.getContorno();
                if (contornoElemento != null) {
                    contorno.append(contornoElemento, false);
                }
            }
        }
        return contorno;
    }

//    public Shape getContorno() {
//
//        if (!tieneElementos()) {
//            contorno = null;
//            return null;
//        }
//
//        if (contorno == null) {
//            contorno = new Area();
//            for (ElementoSVG elemento : lista) {
//                Shape contornoElemento = elemento.getContorno();
//                if (contornoElemento != null) {
//                    contorno.add(new Area(contornoElemento));
//                }
//            }
//        }
//        return contorno;
//    }

    public void pintar(Graphics2D graphics, AjustesGraficos ajustes) {
        for (ElementoSVG elemento : lista) {
            elemento.pintar(graphics, (AjustesGraficos) ajustes.clone());
        }
    }

    /**
     * Recibe la visita de un visitante, visitando tambien todos sus elementos hasta 
     * que el valor retornado sea false.
     */
    public boolean recorrer(Visitante visitante) {

        for (ElementoSVG elemento : lista) {
            if (!elemento.recorrer(visitante)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Itera en la lista de elementos haciendo que un visitante visite todos los elementos.
     * @param visitante El visitante.
     * @return False si se desea interrumpir la iteracción.
     */
    public boolean iterarDescendente(Visitante visitante) {
        return iterar(visitante, lista.descendingIterator());
    }

    public boolean iterarAscendente(Visitante visitante) {
        return iterar(visitante, lista.iterator());
    }

    private boolean iterar(Visitante visitante, Iterator<ElementoSVG> iterador) {
        while (iterador.hasNext()) {
            ElementoSVG elemento = iterador.next();
            if (!visitante.visitar(elemento)) {
                return false;
            }
        }
        return true;
    }

    public void traerAlFrente(ConjuntoElementos listaNodosASubir) {
        ListIterator<ElementoSVG> iterador = lista.listIterator(0);
        List<ElementoSVG> elementosSuperiores = new ArrayList<ElementoSVG>(listaNodosASubir.getLista().size());

        while (iterador.hasNext()) {
            ElementoSVG elemento = iterador.next();

            if (listaNodosASubir.contiene(elemento)) {
                iterador.remove();
                elementosSuperiores.add(elemento);
            }
        }
        lista.addAll(elementosSuperiores);
    }

    public void llevarAlFondo(ConjuntoElementos listaNodosABajar) {
        ListIterator<ElementoSVG> iterador = lista.listIterator(0);
        List<ElementoSVG> elementosInferiores = new ArrayList<ElementoSVG>(listaNodosABajar.getLista().size());

        while (iterador.hasNext()) {
            ElementoSVG elemento = iterador.next();

            if (listaNodosABajar.contiene(elemento)) {
                iterador.remove();
                elementosInferiores.add(elemento);
            }
        }
        lista.addAll(0, elementosInferiores);
    }

    public void subir(ConjuntoElementos listaNodosASubir) {
        ListIterator<ElementoSVG> iterador = lista.listIterator(lista.size());
        ElementoSVG elementoEncima = null;

        while (iterador.hasPrevious()) {
            ElementoSVG elemento = iterador.previous();

            if (elementoEncima != null &&
                    listaNodosASubir.contiene(elemento) &&
                    !listaNodosASubir.contiene(elementoEncima)) {
                iterador.set(elementoEncima);
                iterador.next();
                iterador.next();
                iterador.set(elemento);
                iterador.previous();
                iterador.previous();
            } else {
                elementoEncima = elemento;
            }
        }
    }

    /**
     * Devuelve el subconjuto de elementos que pueden subir un nivel 
     * del conjunto de elementos que se especifica.
     * @param elementos El conjunto de elementos que se desea comprobar si pueden subir.
     * @return El conjunto de elementos que pueden subir un nivel
     */
    public ConjuntoElementos puedenSubir(ConjuntoElementos elementos) {
        ConjuntoElementos elementosASubir = new ConjuntoElementos();

        ListIterator<ElementoSVG> iterador = lista.listIterator(lista.size());
        ElementoSVG elementoEncima = null;
        while (iterador.hasPrevious()) {
            ElementoSVG elemento = iterador.previous();

            if (elementoEncima != null &&
                    elementos.contiene(elemento) &&
                    !elementos.contiene(elementoEncima)) {
                elementosASubir.agregar(elemento);
            } else {
                elementoEncima = elemento;
            }
        }
        return elementosASubir;
    }

    /**
     * Devuelve el subconjuto de elementos que pueden bajar un nivel 
     * del conjunto de elementos que se especifica.
     * @param elementos El conjunto de elementos que se desea comprobar si pueden bajar.
     * @return El conjunto de elementos que pueden bajar un nivel
     */
    public ConjuntoElementos puedenBajar(ConjuntoElementos elementos) {
        ConjuntoElementos elementosABajar = new ConjuntoElementos();

        ListIterator<ElementoSVG> iterador = lista.listIterator(0);
        ElementoSVG elementoDebajo = null;
        while (iterador.hasNext()) {
            ElementoSVG elemento = iterador.next();
            if (elementoDebajo != null &&
                    elementos.contiene(elemento) &&
                    !elementos.contiene(elementoDebajo)) {
                elementosABajar.agregar(elemento);
            } else {
                elementoDebajo = elemento;
            }
        }
        return elementosABajar;
    }

    public void bajar(ConjuntoElementos elementosABajar) {
        ListIterator<ElementoSVG> iterador = lista.listIterator(0);
        ElementoSVG elementoDebajo = null;
        while (iterador.hasNext()) {
            ElementoSVG elemento = iterador.next();
            if (elementosABajar.contiene(elemento) && elementoDebajo != null) {
                iterador.set(elementoDebajo);
                iterador.previous();
                iterador.previous();
                iterador.set(elemento);
                iterador.next();
                iterador.next();
            } else {
                elementoDebajo = elemento;
            }
        }
    }

    public void borrar(ElementoSVG elemento) {
        iteradorAleatorio = null;
        lista.remove(elemento);
    }

    public void borrar(ConjuntoElementos elementos) {
        iteradorAleatorio = null;
        lista.removeAll(elementos.getLista());
    }

    public void borrar(List<ElementoSVG> elementos) {
        iteradorAleatorio = null;
        lista.removeAll(elementos);
    }

    public void desplazar(double dx, double dy) {
        for (ElementoSVG elemento : lista) {
            if (elemento instanceof ElementoGrafico) {
                ((ElementoGrafico) elemento).transformacion().desplazar(dx, dy);
            }
        }
        actualizarContorno();
    }

    public void escalar(Rectangle2D encuadreInicial, Rectangle2D encuadreFinal) {
        for (ElementoSVG elemento : lista) {
            if (elemento instanceof ElementoGrafico) {
            ((ElementoGrafico) elemento).transformacion().escalar(encuadreInicial, encuadreFinal);
            }
        }
        actualizarContorno();
    }

    public void rotar(Angulo anguloRotacion, double x, double y) {
        for (ElementoSVG elemento : lista) {
            if (elemento instanceof ElementoGrafico) {
                ((ElementoGrafico) elemento).transformacion().rotar(anguloRotacion, x, y);
            }
        }
        actualizarContorno();
    }

    public void transformar(final AffineTransform transformacion) {
        for (ElementoSVG elemento : lista) {
            if (elemento instanceof ElementoGrafico) {
                ((ElementoGrafico) elemento).transformar(transformacion);
            }
        }
        actualizarContorno();
    }
}
